package handler;

import configuration.MyConfiguration;
import exception.ReadTimeOutSleepTime;
import http.HttpMethod;
import http.MyHttpRequest;
import http.MyHttpResponse;
import pool.WriteTask;
import pool.ThreadManager;
import util.HttpUtil;
import util.MyUtil;
import util.reg.RegEx;
import util.reg.RegFactory;

import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @author Karl
 * @Date 2023/1/2 - 14:09
 */
public class ForwardHandler implements Handler {

    private static String lineSeparator = System.lineSeparator();
    private static Charset utf_8 = StandardCharsets.UTF_8;
    private static byte[] lineByte = lineSeparator.getBytes(utf_8);
    private static byte[] lineByte2 = (lineSeparator + lineSeparator).getBytes(utf_8);
    //结合请求头的最大长度，这里我选择8KB

    //缓存数组的数据量
//    private int off = 0;

    @Override
    public boolean handler(MyHttpRequest request, Socket from, Socket to) {
        try {
            request.sendOrigin(to.getOutputStream(), from.getOutputStream());
            MyHttpResponse response = receiveAndCache(request, to.getInputStream(), from.getOutputStream());

        } catch (IOException e) {
            MyUtil.log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
            return true;
        }
        return false;
    }

    private MyHttpResponse receiveAndCache(MyHttpRequest request, InputStream is, OutputStream os) throws IOException {
        RegEx lineReg = RegFactory.createResponseLineReg();
        MyHttpResponse response = new MyHttpResponse();
        int now = -1, status = -1, sleepTime = 50;
        String line = null;
        byte[] buf = new byte[8192];
        int off = 0;
        // 用来记录当前在读取什么部分
        int responseOrder = 0;
        boolean flag = true;
        while (flag) {
            while (responseOrder == 0 && (now = is.read()) != -1) {
                sleepTime = 50;
                os.write(now);
                //找到请求行
                if ((line = lineReg.enterChar((byte) now)) != null) {
                    response.setResponseLine(line);
                    responseOrder++;
                    //进入找请求体的时候，不确定长度

                    while (responseOrder == 1 && (now = is.read()) != -1) {
                        os.write(now);
                        buf[off++] = (byte) now;
                        if (now == lineByte2[status + 1]) {
                            status++;
                            if (status == 3) {
                                status = -1;
                                break;
                            } else if (status == 1) {
                                response.addResponseHeader(new String(buf, 0, off));
                                off = 0;
                            }
                        } else {
                            status = -1;
                            if (now == lineByte2[status + 1]) {
                                status++;
                            }
                        }
                    }

                    //判断有多少响应体,并且在里面判断是否需要缓存，
                    // 需要缓存的话会将响应体保存起来
                    receiveBody(request, response, is, os);

                    flag = false;

                    if(response.isNeedCache()){
                        //需要缓存起来，启动文件去缓存
                        HttpUtil.saveResponseToDisk(request,response);
                    }
                }

            }
            if(flag){
                try {
                    Thread.sleep(sleepTime);
                    sleepTime +=10;
                    if(sleepTime>550) throw new ReadTimeOutSleepTime("在Forward的receiveAndCache读超时！");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }
        return response;
    }



    /**
     * 判断是否需要接收响应体
     *
     * @param request
     * @param response
     * @param is
     * @param os
     * @return
     */
    private void receiveBody(MyHttpRequest request, MyHttpResponse response, InputStream is, OutputStream os) throws IOException {
        int now = -1, off = 0, status = -1;
        boolean isNeedCache = false;

        //判断是否有响应体
        if (request.getMethod() == HttpMethod.HEAD) {
            return;
        }
        int responseStatus = response.getStatus();
        if (responseStatus >= 100) {
            //1xx不管响应体。直接返回
            if (responseStatus < 200)
                return;
            else if (responseStatus < 300) {
                if (responseStatus == 204) return;
                //其他都可能需要判断是否需要缓存
                //判断是否需要缓存
                if (request.getMethod().equals(HttpMethod.GET)&&HttpUtil.needCache(response)) {
                    //缓存
                    response.setNeedCache(true);
                    isNeedCache = true;
                }
            }
        }
        int len = -1;
        //开始接收响应体
        if ((len = response.getContentLength()) != -1) {
            while (len > 0) {
                while (len > 0 && (now = is.read()) != -1) {
                    os.write(now);
                    if(isNeedCache) response.addResponseBody((byte) now);
                    len--;
                }

            }
        } else if (response.isChunked()) {
            //通过chunked读取响应体
            len = getChunkedLen(is, os, response);
            while (len > 0) {
                while (len > 0) {
                    if ((now = is.read()) != -1) {
                        os.write(now);
                        if(isNeedCache) response.addResponseBody((byte) now);
                        len--;
                        if (len < 2)
                            System.out.println();
                    }
                }
                //写死，读完之后一定是CRLF
                readCRLF(is,os,response);
                len = getChunkedLen(is, os, response);

            }
            readCRLF(is,os,response);
        }
    }

    /**
     * 读取掉is接下来的两个字节，而且必须是\r\n
     * @param is 目标服务器的inputStream
     * @param os 客户端的output
     * @param response 这次响应的HTTP对象
     * @throws IOException 如果读到的不是\r\n就抛异常，结束此次连接
     */
    private void readCRLF(InputStream is, OutputStream os, MyHttpResponse response) throws IOException {
        int now = -1 ;
        boolean isNeedCache = response.isNeedCache();
        while(true){
            if((now = is.read()) > 0){
                os.write(now);
                if(isNeedCache) response.addResponseBody((byte) now);
                if(now!=lineByte[0]) throw new IOException("chunked后不是换行\\r");
                break;
            }
        }
        while(true){
            if((now = is.read()) > 0){
                os.write(now);
                if(isNeedCache) response.addResponseBody((byte) now);
                if(now!=lineByte[1]) throw new IOException("chunked后不是换行\\n");
                break;
            }
        }
    }


    @Override
    public boolean isSupport(MyHttpRequest request,Socket remote) {
        return true;
    }

    /**
     * 读取chunked的首行，即一个十六进制的数表示接下来消息块的长度
     * @param is 目标服务器的inputStream
     * @param os 客户端的output
     * @param response 这次响应的HTTP对象
     * @return  16机制转成十进制的字节数
     * @throws IOException
     */

    private int getChunkedLen(InputStream is, OutputStream os, MyHttpResponse response) throws IOException {
        int status = -1, off = 0, now = -1, len = -1,sleepTime = 50;
        boolean isNeedCache = response.isNeedCache();
        byte[] buf = new byte[32];
        while (true) {
            if ((now = is.read()) != -1){
                sleepTime = 50;
                buf[off++] = (byte) now;
                if(isNeedCache) response.addResponseBody((byte) now);
                os.write(now);
                if (now == lineByte[status + 1]) {
                    status++;
                    if (status == 1) {
                        len = Integer.parseInt(new String(buf, 0, off).trim(), 16);
                        return len;
                    }
                } else {
                    status = -1;
                    if (now == lineByte[status + 1]) {
                        status++;
                    }
                }
            }else{
                try {
                    Thread.sleep(sleepTime);
                    sleepTime+=10;
                    if(sleepTime>450) throw new ReadTimeOutSleepTime("在getChunkedLen读取超时");
                } catch (InterruptedException e) {
                    return 0;
                }

            }
        }
    }



}
